/*
*    Phantom v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include "SSI_socket_stream.h"

#include <iostream>
#include ".\ssi_socket_stream.h"
using namespace std;

#define  AGENT_ERR0001         " ** Agent Error: Expecting command data"
#define  AGENT_ERR0002         " ** Agent Error: Invalid Command Sequence"

socket_stream::socket_stream(void)
: m_Engine(NULL)
, m_SentCtr(0)
, m_RecvCtr(0)
{
	m_Socket = 0;
}

socket_stream::~socket_stream(void)
{
}


void socket_stream::operator <<(const char* In){
	SendOutput(In, 1, SSI_RESPONSE_OUTPUT);
}

void socket_stream::operator >>(const char* In){
}

void socket_stream::SendOutput(const char *In, SSI_BOOL bAddLine, SSI_INT64 Type){
	SSI_VERBOSE_LOG("stream: Begin");
	if(!m_Engine) return;

	int len = (int)strlen(In);

	char* sData = new char[len + 2];
	sData = strcpy(sData, In);

	if(bAddLine){
		sData[len] = '\n';
		len++;
	}
	sData[len] = 0;

	SSI_VERBOSE_LOG("stream: Response");
	m_Cmd.Set(Type, (int)strlen(sData), sData);
	if(!m_Cmd.SendMe(*m_Socket)){
		m_Engine->GetInterpreter()->SetStopFlag();
		delete [] sData;
		return;
	}
	m_SentCtr++;

	SSI_VERBOSE_LOG("stream: Response Response");
	if(!m_Cmd.ReadMe(*m_Socket)){
		m_Engine->GetInterpreter()->SetStopFlag();
		delete [] sData;
		return;
	}
	m_RecvCtr++;

	if(!m_Cmd.IsValidSequence()){
		m_Cmd.Set(RSP_NACK, strlen(AGENT_ERR0002), AGENT_ERR0002);
		m_Cmd.SendMe(*m_Socket);
		m_SentCtr++;
		m_Engine->GetInterpreter()->SetStopFlag();
		delete [] sData;
		return;
	}

	if(m_Cmd.ID == RSP_ERROR){
		m_Engine->GetInterpreter()->SetStopFlag();
		delete [] sData;
		return;
	}

	if(m_Cmd.ID == RSP_NACK){
		delete [] sData;
		Log("** NACK Received from Response");
		m_Engine->GetInterpreter()->SetStopFlag();
		return;
	}

	SSI_VERBOSE_LOG("stream: Done");
	delete [] sData;
}

void socket_stream::Disp(const char *In, SSI_BOOL bAddLine)
{
	SendOutput(In, bAddLine, SSI_RESPONSE_OUTPUT);
}

SSI_INT64 socket_stream::getCmd()
{
	if(!m_Engine) return 0;

	// Requests and gets the next command in the list
	if(!m_Socket) return 0;

	m_Cmd.Set(SSI_LINE_REQUEST, 0, 0);
	if(!m_Cmd.SendMe(*m_Socket)){
		return 0;
	}
	m_SentCtr++;

	if(!m_Cmd.ReadMe(*m_Socket)){
		return 0;
	}
	m_RecvCtr++;

	if(!m_Cmd.IsValidSequence()){
		m_Cmd.Set(RSP_NACK, strlen(AGENT_ERR0002), AGENT_ERR0002);
		m_Cmd.SendMe(*m_Socket);
		m_SentCtr++;
		return 0;
	}
	return 1;
}

SSI_INT64 socket_stream::getDebug(SSI_INT64 LineNum, const SSI_CHAR* File, SSI_CHAR* Buff, SSI_INT64 sz){
	if(!m_Engine) return -1;

	// Requests and gets the next command in the list
	if(!m_Socket) return -1;

	String s;
	s.Format("{%d} %s", LineNum, File);

	m_Cmd.Set(SSI_DBG_RQST, s.GetLength(), s.Str());
	if(!m_Cmd.SendMe(*m_Socket)){
		return -1;
	}
	m_SentCtr++;

	if(!m_Cmd.ReadMe(*m_Socket)){
		return -1;
	}
	m_RecvCtr++;

	if(!m_Cmd.IsValidSequence()){
		m_Cmd.Set(RSP_NACK, strlen(AGENT_ERR0002), AGENT_ERR0002);
		m_Cmd.SendMe(*m_Socket);
		m_SentCtr++;
		return -1;
	}

	// Check if valid debug request:
	//   1.  Variable
	//   2.  Breakpoint
	//   3.  Step

	if(m_Cmd.ID == SSI_VAR_RQST){
		if(m_Cmd.Size > (sz - 2)){
			m_Cmd.Set(RSP_NACK, strlen(AGENT_ERR0001), AGENT_ERR0001);
			m_Cmd.SendMe(*m_Socket);
			m_SentCtr++;
			return -1;
		}
		String s = "v ";
		s = s + (char*)m_Cmd.Data;
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_BPT_ADD){
		String s = "+b ";
		s = s + (char*)m_Cmd.Data;
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_DBG_EVAL){
		String s = "e ";
		s = s + (char*)m_Cmd.Data;
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_BPT_RMV){
		String s = "-b ";
		s = s + (char*)m_Cmd.Data;
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_DBG_GOCTR){
		String s = "g ";
		s = s + (char*)m_Cmd.Data;
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_DBG_TOLIN){
		String s = "t ";
		s = s + (char*)m_Cmd.Data;
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_DBG_JMP){
		String s = "j";
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_DBG_OUT){
		String s = "o";
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_DBG_STEP){
		String s = "g 1";
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else if(m_Cmd.ID == SSI_BYE){
		String s = "stop";
		Buff = strcpy(Buff, (const char*)s.Str());
		return strlen(Buff);
	}else{
		m_Cmd.Set(RSP_NACK, strlen(AGENT_ERR0001), AGENT_ERR0001);
		m_Cmd.SendMe(*m_Socket);
		m_SentCtr++;
		return -1;
	}

	return -1;
}

SSI_INT64 socket_stream::sendDebug()
{
	if(!m_Engine) return 0;

	// Requests and gets the next command in the list
	if(!m_Socket) return 0;
	
	// Assuming m_Cmd has been set
	if(!m_Cmd.SendMe(*m_Socket)){
		return 0;
	}
	m_SentCtr++;

	if(!m_Cmd.ReadMe(*m_Socket)){
		return 0;
	}
	m_RecvCtr++;

	if(!m_Cmd.IsValidSequence() || m_Cmd.ID != RSP_ACK){
		m_Cmd.Set(RSP_NACK, strlen(AGENT_ERR0001), AGENT_ERR0001);
		m_Cmd.SendMe(*m_Socket);
		m_SentCtr++;
		return 0;
	}
	return 1;
}

SSI_INT64 socket_stream::getline(SSI_CHAR* Buff, SSI_INT64 sz)
{
	// OK, heres the deal
	// getline will not get called, but the function that calls
	//  getline will get overridden
	// This class will have a function getCommand, and the 
	//  calling class will handle the result from the 
	//  line request...

	if(!m_Engine){
		Buff[0] = 0;
		return -1;
	}

	if(getCmd() == 0){
		Buff[0] = 0;
		return -1;
	}

	while(m_Cmd.ID != SSI_COMMAND){
		if(getCmd() == 0){
			Buff[0] = 0;
			return -1;
		}
	}

	// A command was received
	if(m_Cmd.Size > sz){
		m_Cmd.Set(RSP_NACK, strlen(AGENT_ERR0001), AGENT_ERR0001);
		m_Cmd.SendMe(*m_Socket);
		m_SentCtr++;
		Buff[0] = 0;
		return -1;
	}
	
	if(m_Cmd.Data){
		Buff = strcpy(Buff, m_Cmd.Data);
	}else{
		sprintf(Buff, "");
	}
	m_Cmd.Set(RSP_ACK, 0, 0);
	if(!m_Cmd.SendMe(*m_Socket)){
		Buff[0] = 0;
		return -1;
	}
	m_SentCtr++;

	return (SSI_INT64)strlen(Buff);
}

void socket_stream::ErrorDisp(const char* In, SSI_BOOL bAddLine)
{
	SendOutput(In, bAddLine, SSI_ERROR_OUTPUT);
}

void socket_stream::WarningDisp(const char* In, SSI_BOOL bAddLine)
{
	SendOutput(In, bAddLine, SSI_WARNING_OUTPUT);
}

void socket_stream::FailureDisp(const char* In, SSI_BOOL bAddLine)
{
	SendOutput(In, bAddLine, SSI_FAILURE_OUTPUT);
}

void socket_stream::CleanUp(void)
{
	if(m_Socket){
		CSocketSSI::Disconnect(m_Socket->myHandle);
		m_Socket = 0;
	}
}

void socket_stream::CloseConnection(void)
{

	CSockCmd aCmd;
	//SSI_VERBOSE_LOG("stream: SENDING BYE");
	//aCmd.Set(SSI_BYE, 0, 0);
	//aCmd.SendMe(*m_Socket);
	CSocketSSI::Disconnect(m_Socket->myHandle);
	m_Socket = 0;
	m_SentCtr++;
}

int socket_stream::SetLogFile(const char* InFile)
{
	//FILE* LogFile = fopen(InFile, "a");	
	//if(!LogFile) return 0;
	//sLogFile = InFile;
	//fprintf(LogFile, "-------------------------------------------\n");
	//fclose(LogFile);
	return 1;
}

void socket_stream::Log(const char* Input)
{
	//FILE* LogFile = fopen(sLogFile.c_str(), "a");	
	//if(LogFile){
	//	fprintf(LogFile, "%s\n", Input);
	//	fclose(LogFile);
	//}
	//printf("%s\n", Input);
}

void socket_stream::SetSocket(CSocketSSI* NewSocket)
{
	m_Socket = NewSocket;
}


void socket_stream::SetEngine(CEngine* InEngine)
{
	m_Engine = InEngine;
}

SSI_INT64 socket_stream::getSentCtr(void)
{
	return m_SentCtr;
}

SSI_INT64 socket_stream::getRecvCtr(void)
{
	return m_RecvCtr;
}

int socket_stream::getLocalFile(const String& FileName, String& LocName){

  LocName = FileName;
  /*
	LocName = FileName;
	return 1;

	// Will need to revisit this in the future...

	int iSize;
	
	if(!m_Engine){
		return SSI_NULL;
	}

	m_Cmd.Set(SSI_FILE_REQUEST, FileName.GetLength(), FileName.Str());
	if(!m_Cmd.SendMe(*m_Socket)){
		return SSI_NULL;
	}
	m_SentCtr++;

	if(!m_Cmd.ReadMe(*m_Socket)){
		return SSI_NULL;
	}
	m_RecvCtr++;

	FILE* fh = fopen("clobber.dat", "wb");
	if(fh == SSI_NULL){
		m_Cmd.Set(RSP_NACK, 0, 0);
		if(!m_Cmd.SendMe(*m_Socket)){
			return SSI_NULL;
		}
		m_SentCtr++;

		return SSI_NULL;
	}


	while(m_Cmd.ID == SSI_FILE_RESPONSE){	
    
		iSize = m_Cmd.Size;
		if(iSize > 0){
			fwrite((const void*)m_Cmd.Data, 1, m_Cmd.Size, fh);
		}

		m_Cmd.Set(RSP_ACK, 0, 0);
		if(!m_Cmd.SendMe(*m_Socket)){
			return SSI_NULL;
		}
		m_SentCtr++;

		if(iSize <= 0){
			break;
		}

		if(!m_Cmd.ReadMe(*m_Socket)){
			return SSI_NULL;
		}
		m_RecvCtr++;
	}



	fclose(fh);
	LocName = "clobber.dat";
  */
	return 1;

}

SSI_INT64 socket_stream::Initialize(SSI_INT64& Type, SSI_INT64& Mode, String& Script, String& ScriptArgs)
{
	CSockCmd aCmd;

	SSI_INT64 Flags = 0;
	Mode = NORMAL_MODE;

	SSI_VERBOSE_LOG("Connection: Sending Action");
	aCmd.Set(SSI_ACTION_REQUEST, 0, 0);
	if(!aCmd.SendMe(*m_Socket)){
		CloseConnection();
		CleanUp();
		return 3;
	}
	m_SentCtr++;


  // TRY USING SELECT FUNCTION TO DETERMINE IF DATA IS AVAILABLE!  ELSE, CAN BECOME BLOCKED

  SSI_VERBOSE_LOG("Connection: Reading Action");
	if(!aCmd.ReadMe(*m_Socket)){
		SSI_VERBOSE_LOG("Could not read action response.\n");
		CloseConnection();
		CleanUp();
		return 4;
	}
	m_RecvCtr++;
	SSI_VERBOSE_LOG("Connection: Checking Response");

	if(aCmd.ID != SSI_ACTION_RESPONSE){
		CloseConnection();
		CleanUp();
		return 5;
	}

	SSI_VERBOSE_LOG("Connection: Checking Mode");
	if(aCmd.Size == 0){
		Type = 0;    // Console mode
	}else{
		Type = 1;    // Script mode
		Script = (const char*)(aCmd.Data);
		if(Script.GetLength() != aCmd.Size){
			String sErr = "Invalid file name";
			aCmd.Set(RSP_NACK, sErr.GetLength(), sErr.Str());
			aCmd.SendMe(*m_Socket);
			m_SentCtr++;
			CloseConnection();
			CleanUp();
			return 6;
		}
	}

	SSI_VERBOSE_LOG("Connection: ACK 1");
	aCmd.Set(RSP_ACK, 0, 0);
	if(!aCmd.SendMe(*m_Socket)){
		CloseConnection();
		CleanUp();
		return 7;
	}
	m_SentCtr++;

	SSI_VERBOSE_LOG("Connection: Flag Request");
	// Read Flags
	aCmd.Set(SSI_FLAG_REQUEST, 0, 0);
	if(!aCmd.SendMe(*m_Socket)){
		CloseConnection();
		CleanUp();
		return 8;
	}
	m_SentCtr++;

	if(!aCmd.ReadMe(*m_Socket)){
		CloseConnection();
		CleanUp();
		return 9;
	}
	m_RecvCtr++;

	SSI_VERBOSE_LOG("Connection: Checking Flag Response");
	if(aCmd.ID != SSI_FLAG_RESPONSE){
		CloseConnection();
		CleanUp();
		return 10;
	}

	if(aCmd.Size != 4){
		String sErr = "Invalid flag packet";
		aCmd.Set(RSP_NACK, sErr.GetLength(), sErr.Str());
		aCmd.SendMe(*m_Socket);
		m_SentCtr++;
		CloseConnection();
		CleanUp();
		return 11;
	}

	Flags = *(int*)(aCmd.Data);
	if(Flags & DEBUG_BIT){
		Mode = DEBUG_MODE;
	}

	SSI_VERBOSE_LOG("Connection: ACK 2");
	aCmd.Set(RSP_ACK, 0, 0);
	if(!aCmd.SendMe(*m_Socket)){
		CloseConnection();
		CleanUp();
		return 12;
	}
	m_SentCtr++;

  if(Flags & ARGS_BIT){
		// Read Flags
		aCmd.Set(SSI_ARGS_REQUEST, 0, 0);
		if(!aCmd.SendMe(*m_Socket)){
			CloseConnection();
			CleanUp();			
      return 13;
		}
    m_SentCtr++;

		if(!aCmd.ReadMe(*m_Socket)){
			CloseConnection();
			CleanUp();
			return 14;
		}
    m_RecvCtr++;

		if(aCmd.ID != SSI_ARGS_RESPONSE){
			CloseConnection();
			CleanUp();
			return 15;
		}

    ScriptArgs = aCmd.Data;

	  SSI_VERBOSE_LOG("Arguments: ACK");
	  aCmd.Set(RSP_ACK, 0, 0);
	  if(!aCmd.SendMe(*m_Socket)){
		  CloseConnection();
		  CleanUp();
		  return 16;
	  }
	  m_SentCtr++;
  }

  return 0;
}
